#include<bits/stdc++.h>
#include <iostream>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>

#define THREAD_COUNT 4
#define fi first
#define se second
#define pii pair<int, int>
using namespace std;

namespace fastIO {
#define BUF_SIZE 100000
#define OUT_SIZE 100000
#define ll long long
    bool IOerror = 0;

    inline char nc() {
        static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
        if (p1 == pend) {
            p1 = buf;
            pend = buf + fread(buf, 1, BUF_SIZE, stdin);
            if (pend == p1) {
                IOerror = 1;
                return -1;
            }
        }
        return *p1++;
    }

    inline bool blank(char ch) { return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'; }

    inline bool read(int &x) {
        bool sign = 0;
        char ch = nc();
        x = 0;
        for (; blank(ch); ch = nc());
        if (IOerror)return false;
        if (ch == EOF) return false;
        if (ch == '-')sign = 1, ch = nc();
        for (; ch >= '0' && ch <= '9'; ch = nc())x = x * 10 + ch - '0';
        if (sign)x = -x;
        return true;
    }

    struct Ostream_fwrite {
        char *buf, *p1, *pend;

        Ostream_fwrite() {
            buf = new char[BUF_SIZE];
            p1 = buf;
            pend = buf + BUF_SIZE;
        }

        void out(char ch) {
            if (p1 == pend) {
                fwrite(buf, 1, BUF_SIZE, stdout);
                p1 = buf;
            }
            *p1++ = ch;
        }

        void print(int x) {
            static char s[15], *s1;
            s1 = s;
            if (!x)*s1++ = '0';
            if (x < 0)out('-'), x = -x;
            while (x)*s1++ = x % 10 + '0', x /= 10;
            while (s1-- != s)out(*s1);
        }

        void print(char *s) { while (*s)out(*s++); }

        void println(char *s) {
            while (*s)out(*s++);
            out('\n');
        }

        void flush() {
            if (p1 != buf) {
                fwrite(buf, 1, p1 - buf, stdout);
                p1 = buf;
            }
        }

        ~Ostream_fwrite() { flush(); }
    } Ostream;

    inline void print(int x) { Ostream.print(x); }

    inline void print(char x) { Ostream.out(x); }

    inline void println(int x) {
        Ostream.print(x);
        Ostream.out('\n');
    }

    inline void printd(int x) {
        Ostream.print(x);
        Ostream.out(',');
    }

    inline void println(char x) {
        Ostream.out(x);
        Ostream.out('\n');
    }

    inline void print(char *s) { Ostream.print(s); }

    inline void println(char *s) { Ostream.println(s); }

#undef ll
#undef OUT_SIZE
#undef BUF_SIZE
};
using namespace fastIO;
using namespace std;
const int N = 3e6 + 5;
const int M = 6e5 + 5;
const char numchar[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
int m, n;
int val[M];
pii p[M];
string numstring[M];
string ansstring;

bool cmp(pii a, pii b) {
    return a.se > b.se;
}


struct G_Edge {
    struct Edge {
        int v, next;
    } edge[M];

    int head[M], cnt;

    void init(int n) {
        if (cnt != 0) n = cnt;
        cnt = 0;
        memset(head, -1, sizeof(int) * n);
    }

    void add(int u, int v) {
        edge[cnt] = {v, head[u]};
        head[u] = cnt++;
    }
} G1, G2, G3;

struct find_huan {
    bool flag[M], f[M];
    int len[N], cur[15];
    int **ans;
    int is[M], num;
    pii que[M];
    int _head, _tail;
    int offset;

    inline void add_ans(int now_len) {
        ans[num][1] = cur[1];
        ans[num][2] = cur[2];
        ans[num][3] = cur[3];
        if (now_len == 3) return;
        ans[num][4] = cur[4];
        if (now_len == 4) return;
        ans[num][5] = cur[5];
        if (now_len == 5) return;
        ans[num][6] = cur[6];
        if (now_len == 6) return;
        ans[num][7] = cur[7];
    }


    void dfs(int u, int step, int rt) {
        if (u < rt || flag[u]) return;
        if (step > 4 && is[u] != rt) return;
        cur[step] = u;
        if (step >= 3 && f[u]) {
            len[++num] = step;
            add_ans(step);
        }
        if (step >= 7) return;
        flag[u] = true;
        for (int i = G1.head[u]; ~i; i = G1.edge[i].next) {
            dfs(G1.edge[i].v, step + 1, rt);
        }
        flag[u] = false;
    }


    void bfs(int rt) {
        pii te;
        int u;
        while (_tail > _head) {
            te = que[++_head];
            u = te.fi;
            if (te.se == 3) continue;
            for (int i = G3.head[u]; ~i; i = G3.edge[i].next) {
                if (is[G3.edge[i].v] == rt || G3.edge[i].v < rt) continue;
                is[G3.edge[i].v] = rt;
                que[++_tail] = {G3.edge[i].v, te.se + 1};
            }
        }
    }

    void start(int l, int r) {
        if (l > r) return;

        bool ok = true;
        for (int i = l; i <= r; i++) {
            cur[1] = i;
            ok = false;
            is[i] = i;
            _head = _tail = 0;
            for (int j = G2.head[i]; ~j; j = G2.edge[j].next) {
                ok = true;
                f[G2.edge[j].v] = true;
                que[++_tail] = {G2.edge[j].v, 1};
                is[G2.edge[j].v] = i;
            }


            if (ok) {
                bfs(i);
                dfs(i, 1, i);
                for (int j = G2.head[i]; ~j; j = G2.edge[j].next) {
                    f[G2.edge[j].v] = false;
                }
            }
        }
    }
} T[4];

struct tag_mmap {
    // 要读取源文件的起始内存位置
    void *r_mem;
    // 要写入目标文件的起始内存位置
    void *w_mem;
    // 要复制的大小
    unsigned long long m_size;
};

//多线程拷贝
void threadfunc(tag_mmap arg) {
    char *r = (char *) arg.r_mem;
    char *w = (char *) arg.w_mem;
    memcpy(w, r, arg.m_size);
}


inline int get(int x) {
    return lower_bound(val + 1, val + n + 1, x) - val;
}

//bool flag[THREAD_COUNT][M], in[M], f[THREAD_COUNT][M];
int sta[M], dfn[M], low[M], bl[M], res, tail, k;
bool in[M];
//int is[THREAD_COUNT][M];

inline void pr(int d[], int now_len) {
    ansstring += numstring[d[1]];
    ansstring += ',';
    ansstring += numstring[d[2]];
    ansstring += ',';
    if (now_len == 3) {
        ansstring += numstring[d[3]];
        ansstring += '\n';
        return;
    }
    ansstring += numstring[d[3]];
    ansstring += ',';
    if (now_len == 4) {
        ansstring += numstring[d[4]];
        ansstring += '\n';
        return;
    }
    ansstring += numstring[d[4]];
    ansstring += ',';
    if (now_len == 5) {
        ansstring += numstring[d[5]];
        ansstring += '\n';
        return;
    }
    ansstring += numstring[d[5]];
    ansstring += ',';
    if (now_len == 6) {
        ansstring += numstring[d[6]];
        ansstring += '\n';
        return;
    }
    ansstring += numstring[d[6]];
    ansstring += ',';
    ansstring += numstring[d[7]];
    ansstring += '\n';
}

void tarjan(int u) {
    dfn[u] = low[u] = ++res;
    sta[++tail] = u;
    in[u] = true;
    int v;
    for (int i = G1.head[u]; ~i; i = G1.edge[i].next) {
        v = G1.edge[i].v;
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (in[v]) {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (low[u] == dfn[u]) {
        ++k;
        do {
            v = sta[tail--];
            in[v] = false;
            bl[v] = k;
        } while (v != u);
    }
}

void prtime(char *pos, timeval &time) {
    print(pos);
    print("=    ");
    struct timeval now;
    gettimeofday(&now, NULL);
    print(int((now.tv_sec * 1000 + now.tv_usec / 1000) - (time.tv_sec * 1000 + time.tv_usec / 1000)));
    print("ms");
    print("\n");
    time=now;
}

inline void mmap_readdata(char *filename) {
    char *data = NULL;
    int fd = open(filename, O_RDONLY);
    long size = lseek(fd, 0, SEEK_END);
    data = (char *) mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    int u, v, cost;
    int tmp = 0;
    int i = 0, j = 0;
    while (data[i] != '\0') {
        if (data[i] >= '0' && data[i] <= '9') {
            tmp *= 10;
            tmp += data[i] - '0';
        } else if (data[i] == ',') {
            if (j == 0) {
                u = tmp;
                j++;
            } else if (j == 1) {
                v = tmp;
                p[++m] = {u, v};
                val[++n] = u;
                val[++n] = v;
            }
            tmp = 0;
        } else {
            cost = tmp;
            //printf("%d\n", w);
            j = 0;
            tmp = 0;
        }
        ++i;
    }
}

string numtostring(int x) {
    string tmp = "";
    if (x == 0) {
        return "0";
    }
    while (x != 0) {
        tmp += numchar[x % 10];
        x /= 10;
    }
    if (tmp.size() == 1)return tmp;
    reverse(tmp.begin(), tmp.end());
    return tmp;
}

void find_thread(int tid, int s, int t,int offset) {
    T[tid].offset=offset;
    T[tid].start(s, t);
}

int main() {
    timeval start;
    gettimeofday(&start, NULL);
    char *readfile = "./data/2861665/test_data.txt";
    char *outfile = "./z.out";
//    char *readfile = "/data/test_data.txt";
//    char *outfile = "/projects/student/output.txt";

//    freopen(outfile, "w", stdout);
//    fastio_readdata(readfile);
    mmap_readdata(readfile);

    // 离散化
    G1.cnt = G2.cnt = G3.cnt = 0;
    sort(val + 1, val + n + 1);
    n = unique(val + 1, val + n + 1) - val - 1;
    for (int i = 1; i <= n; i++) {
        numstring[i] = numtostring(val[i]);
    }

    sort(p + 1, p + m + 1, cmp);
    G1.init(n + 5);
    G2.init(n + 5);
    for (int i = 1; i <= m; i++) {
        p[i].fi = get(p[i].fi);
        p[i].se = get(p[i].se);
        G1.add(p[i].fi, p[i].se);
    }

    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) {
            tarjan(i);
        }
    }
    G1.init(n + 5);
    G3.init(n + 5);
    for (int i = 1; i <= m; i++) {
        if (bl[p[i].fi] == bl[p[i].se]) {
            G1.add(p[i].fi, p[i].se);
            G3.add(p[i].se, p[i].fi);
            if (p[i].fi > p[i].se)
                G2.add(p[i].se, p[i].fi);
        }
    }
    //开启多线程找环
    if (n < 8) {
        T[0].start(1,n);
        ansstring += numtostring(T[0].num);
        ansstring += '\n';
        for (int j = 3; j <= 7; j++) {
                for (int i = 1; i <= T[0].num; i += 4) {
                    if (T[0].len[i] == j) pr(T[0].ans[i], j);
                    if (T[0].len[i+1] == j) pr(T[0].ans[i + 1], j);
                    if (T[0].len[i+2] == j) pr(T[0].ans[i + 2], j);
                    if (T[0].len[i+3] == j) pr(T[0].ans[i + 3], j);
                }
        }
    } else {
        std::thread threads_find[THREAD_COUNT];
        const int nCount = n / THREAD_COUNT;
        for (int i = 0; i < THREAD_COUNT; i++) {
            int s, t;
            if (i < THREAD_COUNT - 1) {
                s = i * nCount + 1;
                t = (i + 1) * nCount;
            } else {
                s = i * nCount + 1;
                t = n;
            }
            threads_find[i] = std::thread(find_thread, i, s, t,i);
        }
        for (int i = 0; i < THREAD_COUNT; ++i) {
            threads_find[i].join();
        }
        //统计结果
        int result = 0;
        for (int i = 0; i < THREAD_COUNT; ++i)result += T[i].num;
        ansstring += numtostring(result);
        ansstring += '\n';
        for (int j = 3; j <= 7; j++) {
            for (int k = 0; k < THREAD_COUNT; ++k)
                for (int i = 1; i <= T[k].num; i += 4) {
                    if (T[k].len[i] == j) pr(T[k].ans[i], j);
                    if (T[k].len[i+1] == j) pr(T[k].ans[i + 1], j);
                    if (T[k].len[i+2] == j) pr(T[k].ans[i + 2], j);
                    if (T[k].len[i+3] == j) pr(T[k].ans[i + 3], j);
                }
        }
    }


    //多线程mmap输出
    int wfd;
    wfd = open(outfile, O_RDWR | O_CREAT | O_TRUNC, 0666);
    lseek(wfd, ansstring.length() - 1, SEEK_SET);
    write(wfd, "\0", 1);
    const long long nCount = ansstring.length() / THREAD_COUNT;
    const long long nPages = nCount - (nCount % 4096);
    const long long nSplit = ansstring.length() - THREAD_COUNT * nPages;
    if (nCount < 4096) {
        void *w_mem = mmap(NULL, ansstring.length(), PROT_WRITE, MAP_SHARED, wfd, 0);
        memcpy(w_mem, ansstring.c_str(), ansstring.length());
        munmap(w_mem, ansstring.length());
    } else {
        struct tag_mmap mm[THREAD_COUNT];
        std::thread threads[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; ++i) {
            if (i < 3) {
                mm[i].r_mem = (void *) (ansstring.c_str() + i * nPages);
                mm[i].w_mem = mmap(NULL, nPages, PROT_WRITE, MAP_SHARED, wfd, i * nPages);
                mm[i].m_size = nPages;
                threads[i] = std::thread(threadfunc, mm[i]);
            } else {
                mm[i].r_mem = (void *) (ansstring.c_str() + i * nPages);
                mm[i].w_mem = mmap(NULL, nPages + nSplit, PROT_WRITE, MAP_SHARED, wfd, i * nPages);
                mm[i].m_size = nPages + nSplit;
                threads[i] = std::thread(threadfunc, mm[i]);
            }
        }
        for (int join = 0; join < THREAD_COUNT; join++) {
            threads[join].join();
        }
        for (int un = 0; un < THREAD_COUNT; un++) {
            munmap(mm[un].r_mem, mm[un].m_size);
            munmap(mm[un].w_mem, mm[un].m_size);
        }
    }

    prtime("总时间 ",start);
    return 0;
}

